package br.com.sgpv2.service.movimentacao;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import br.com.sgpv2.controler.NegocioExeception;
import br.com.sgpv2.entity.evento.TipoEventoEnum;
import br.com.sgpv2.entity.movimentacao.INSSEnum;
import br.com.sgpv2.entity.movimentacao.IRRFEnum;
import br.com.sgpv2.entity.movimentacao.Movimentacao;
import br.com.sgpv2.entity.movimentacao.MovimentacaoDTO;
import br.com.sgpv2.entity.movimentacao.SalarioFamiliaEnum;
import br.com.sgpv2.entity.movimentacao.ValoresEvento;
import br.com.sgpv2.entity.pessoa.Contrato;
import br.com.sgpv2.entity.pessoa.Pessoa;
import br.com.sgpv2.hibernate.contrato.ContratoRepository;
import br.com.sgpv2.hibernate.movimentacao.MovimentacaoRepository;
import br.com.sgpv2.service.contrato.ContratoService;

/**
 * Classe de serviço de Nivel.
 */
@Service(value = "movimentacaoService")
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class MovimentacaoServiceImpl implements MovimentacaoService {

	private static final String DD_MM_YYYY = "dd/MM/yyyy";
	private static final String JA_EXISTE_MOVIMENTACAO_PARA_OS_DADOS_INFORMADOS = "Já existe movimentação para os dados informados";
	private static final String NAO_EXISTE_CONTRATO_PARA_A_MATRICULA_INFORMADA = "Não existe contrato para a matrícula informada";
	private static final double TAXA_FGTS = 0.08;
	private MovimentacaoRepository movimentacaoRepository;
	private ContratoService contratoService;

	private ContratoRepository contratoRepository;

	@Override
	public void salvar(Movimentacao movimentacao) {
		movimentacaoRepository.salvar(movimentacao);

	}

	@Override
	public void alterar(Movimentacao movimentacao) {
		movimentacaoRepository.alterar(movimentacao);

	}

	@Override
	public List<Movimentacao> listar() {
		return movimentacaoRepository.listar();
	}

	@Override
	public void excluir(Movimentacao movimentacao) {
		movimentacaoRepository.excluir(movimentacao);

	}

	@Override
	public Movimentacao buscar(Integer pk) throws NegocioExeception {
		return movimentacaoRepository.buscar(pk);
	}

	@Override
	public Movimentacao calcularValoresTotalMovimentacao(
			Movimentacao movimentacao) throws NumberFormatException,
			NegocioExeception {

		configurarValoresMovimentacao(movimentacao);

		BigDecimal salarioBase = movimentacao.getContrato()
				.getNivel().getValor();
		movimentacao.setSalarioBaseMovimentacao(salarioBase);
		String matriculaContrato = movimentacao.getContrato().getMatricula();
		Date dataMovimentacao = configurarDataMovimentacao(movimentacao);

		calcularDescontosMovimentacao(movimentacao.getValoresEvento(),
				movimentacao, salarioBase, dataMovimentacao, matriculaContrato);

		BigDecimal totalLiquido = new BigDecimal(0);

		if (movimentacao.getProventos() != null) {
			totalLiquido = movimentacao.getProventos()
					.subtract(movimentacao.getDescontos())
					.setScale(2, RoundingMode.HALF_EVEN);
		}

		movimentacao.setLiquido(totalLiquido);

		return movimentacao;
	}

	private void configurarValoresMovimentacao(Movimentacao movimentacao) {
		movimentacao.setProventos(new BigDecimal(0));
		movimentacao.setDescontos(new BigDecimal(0));
		movimentacao.setFgts(new BigDecimal(0));
		movimentacao.setInss(new BigDecimal(0));
		movimentacao.setIrrf(new BigDecimal(0));
		movimentacao.setSalarioFamilia(new BigDecimal(0));
		movimentacao.setLiquido(new BigDecimal(0));
	}

	private Date configurarDataMovimentacao(Movimentacao movimentacao) {
		String mesMovimentacao = movimentacao.getMes().toString();
		String anoMovimentacao = movimentacao.getAno().toString();

		Date dataMovimentacao = new Date();

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(dataMovimentacao);
		calendar.add(Calendar.MONTH, Integer.parseInt(mesMovimentacao));
		calendar.add(Calendar.YEAR, Integer.parseInt(anoMovimentacao));
		return dataMovimentacao;
	}

	private void calcularDescontosMovimentacao(
			List<ValoresEvento> listaValoresEvento, Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato) {

		for (ValoresEvento valorEnvento : listaValoresEvento) {
			if (TipoEventoEnum.PROVENTO.equals(valorEnvento.getEvento()
					.getTipoEventoEnum())) {
				calcularEventoProvento(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);

			}
			if (TipoEventoEnum.DESCONTO.equals(valorEnvento.getEvento()
					.getTipoEventoEnum())) {
				calcularEventoDesconto(movimentacao, salarioBase, valorEnvento);
			}
			if (TipoEventoEnum.DECIMO_TERCEIRO.equals(valorEnvento.getEvento()
					.getTipoEventoEnum())) {
				configurarDescontosDecimoFerias(valorEnvento);
				calcularEventoDecimo(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);

			}
			if (TipoEventoEnum.DECIMO_TERCEIRO_PRIMEIRA.equals(valorEnvento
					.getEvento().getTipoEventoEnum())) {
				configurarDescontosDecimoPrimeiraParcela(valorEnvento);
				calcularEventoDecimoPrimeiraParcela(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);
			}
			if (TipoEventoEnum.DECIMO_TERCEIRO_SEGUNDA.equals(valorEnvento
					.getEvento().getTipoEventoEnum())) {
				configurarDescontosDecimoFerias(valorEnvento);
				calcularEventoDecimoSegundaParcela(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);
			}
			if (TipoEventoEnum.FERIAS_30.equals(valorEnvento.getEvento()
					.getTipoEventoEnum())) {
				configurarDescontosDecimoFerias(valorEnvento);
				calcularEventoFeriasCheia(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);
			}
			if (TipoEventoEnum.FERIAS_20.equals(valorEnvento.getEvento()
					.getTipoEventoEnum())) {
				configurarDescontosDecimoFerias(valorEnvento);
				calcularEventoFeriasParcial(movimentacao, salarioBase,
						dataMovimentacao, matriculaContrato, valorEnvento);

			}

		}
	}

	private void configurarDescontosDecimoPrimeiraParcela(
			ValoresEvento valorEnvento) {
		valorEnvento.getEvento().setDescontaINSS(false);
		valorEnvento.getEvento().setDescontaIRRF(false);
		valorEnvento.getEvento().setDescontaFGTS(false);
		valorEnvento.getEvento().setDescontaSalarioFamilia(false);

	}

	private void configurarDescontosDecimoFerias(ValoresEvento valorEnvento) {
		valorEnvento.getEvento().setDescontaINSS(true);
		valorEnvento.getEvento().setDescontaIRRF(true);
		valorEnvento.getEvento().setDescontaFGTS(false);
		valorEnvento.getEvento().setDescontaSalarioFamilia(false);
	}

	private void calcularEventoFeriasParcial(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {
		BigDecimal provento = salarioBase.multiply(new BigDecimal(2)).divide(
				new BigDecimal(3), 2, RoundingMode.HALF_EVEN);

		provento = provento.add(provento.divide(new BigDecimal(3), 2,
				RoundingMode.HALF_EVEN));

		provento = provento.add(salarioBase.divide(new BigDecimal(3), 2,
				RoundingMode.HALF_EVEN));

		valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setProventos(movimentacao.getProventos().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));

		calcularDescontosImpostos(movimentacao, salarioBase, dataMovimentacao,
				matriculaContrato, valorEnvento, provento);

	}

	private void calcularEventoFeriasCheia(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {
		BigDecimal provento = salarioBase.add(salarioBase.divide(
				new BigDecimal(3), 2, RoundingMode.HALF_EVEN));

		valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setProventos(movimentacao.getProventos().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));

		calcularDescontosImpostos(movimentacao, salarioBase, dataMovimentacao,
				matriculaContrato, valorEnvento, provento);

	}

	private void calcularEventoDecimo(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {

		BigDecimal provento = obterValorProventoDecimoTerceiro(movimentacao,
				salarioBase);

		valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setProventos(movimentacao.getProventos().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));

		calcularDescontosImpostos(movimentacao, salarioBase, dataMovimentacao,
				matriculaContrato, valorEnvento, provento);

	}

	private void calcularEventoDecimoPrimeiraParcela(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {

		BigDecimal provento = obterValorProventoDecimoTerceiro(movimentacao,
				salarioBase);

		provento = provento.divide(new BigDecimal(2.00), 2,
				RoundingMode.HALF_EVEN);

		valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setProventos(movimentacao.getProventos().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));
	}

	private void calcularEventoDecimoSegundaParcela(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {

		BigDecimal provento = obterValorProventoDecimoTerceiro(movimentacao,
				salarioBase);

		provento = provento.divide(new BigDecimal(2.00), 2,
				RoundingMode.HALF_EVEN);

		valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setProventos(movimentacao.getProventos().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));

		calcularDescontosImpostos(movimentacao, salarioBase, dataMovimentacao,
				matriculaContrato, valorEnvento,
				provento.multiply(new BigDecimal(2.00)));
	}

	private BigDecimal obterValorProventoDecimoTerceiro(
			Movimentacao movimentacao, BigDecimal salarioBase) {

		Date dataInicioContrato = null;
		try {
			dataInicioContrato = new SimpleDateFormat(DD_MM_YYYY)
					.parse(movimentacao.getContrato().getDataInicio());
		} catch (ParseException e) {
		}

		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(dataInicioContrato);
		int mesInicioContrato = calendar.get(GregorianCalendar.MONTH) + 1;
		int quantidadeMeses = 13 - mesInicioContrato;

		BigDecimal provento = new BigDecimal(0);

		provento = salarioBase.divide(new BigDecimal(12.00), 2,
				RoundingMode.HALF_EVEN);
		provento = provento.multiply(new BigDecimal(quantidadeMeses));
		return provento;
	}

	private void calcularEventoDesconto(Movimentacao movimentacao,
			BigDecimal salarioBase, ValoresEvento valorEnvento) {
		BigDecimal desconto = new BigDecimal(0);
		if (valorEnvento.getEvento().getIsEventoPercentual()) {
			desconto = salarioBase.multiply(new BigDecimal(valorEnvento
					.getEvento().getPercentual()).divide(new BigDecimal(100)));
			movimentacao.setDescontos(movimentacao.getDescontos().add(desconto)
					.setScale(2, RoundingMode.HALF_EVEN));
			valorEnvento.setValor(desconto.setScale(2, RoundingMode.HALF_EVEN));
		} else {
			if (!valorEnvento.getEvento().getDescontaSalarioFamilia()) {
				desconto = valorEnvento.getValor();
				movimentacao.setDescontos(movimentacao.getDescontos()
						.add(desconto).setScale(2, RoundingMode.HALF_EVEN));
			}
		}
	}

	private void calcularEventoProvento(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEnvento) {
		BigDecimal provento = new BigDecimal(0);
		if (valorEnvento.getEvento().getIsEventoPercentual()) {
			provento = salarioBase.multiply(new BigDecimal(valorEnvento
					.getEvento().getPercentual()).divide(new BigDecimal(100)));
			movimentacao.setProventos(movimentacao.getProventos().add(provento)
					.setScale(2, RoundingMode.HALF_EVEN));
			valorEnvento.setValor(provento.setScale(2, RoundingMode.HALF_EVEN));
		} else {
			if (!valorEnvento.getEvento().getDescontaSalarioFamilia()) {
				provento = valorEnvento.getValor();
				movimentacao.setProventos(movimentacao.getProventos()
						.add(provento).setScale(2, RoundingMode.HALF_EVEN));
			}
		}

		calcularDescontosImpostos(movimentacao, salarioBase, dataMovimentacao,
				matriculaContrato, valorEnvento, provento);
	}

	private void calcularDescontosImpostos(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato, ValoresEvento valorEvento,
			BigDecimal provento) {
		if (valorEvento.getEvento().getDescontaINSS()) {
			calcularDescontoINSS(movimentacao, provento, dataMovimentacao);
		}

		if (valorEvento.getEvento().getDescontaIRRF()) {
			calcularDescontoIRRF(movimentacao, provento, dataMovimentacao,
					matriculaContrato);
		}
		if (valorEvento.getEvento().getDescontaSalarioFamilia()) {
			calcularDescontoSalarioFamilia(movimentacao, salarioBase,
					dataMovimentacao, matriculaContrato);
		}
		if (valorEvento.getEvento().getDescontaFGTS()) {
			calcularDescontoFGTS(movimentacao, provento, dataMovimentacao);
		}
	}

	private void calcularDescontoFGTS(Movimentacao movimentacao,
			BigDecimal provento, Date dataMovimentacao) {
		// FGTS Não desconta do trabalhador, apenas entra na folha e precisa
		// ficar registrado.
		BigDecimal descontoFgts = provento.multiply(new BigDecimal(TAXA_FGTS));
		movimentacao.setFgts(movimentacao.getFgts().add(descontoFgts)
				.setScale(2, RoundingMode.HALF_EVEN));

	}

	private void calcularDescontoSalarioFamilia(Movimentacao movimentacao,
			BigDecimal salarioBase, Date dataMovimentacao,
			String matriculaContrato) {

		Contrato contrato = contratoRepository
				.buscarPorMatricula(matriculaContrato);
		Pessoa titular = contrato.getPessoa();

		BigDecimal valorBeneficioSalarioFamilia = new BigDecimal(
				SalarioFamiliaEnum.obterTaxa(dataMovimentacao,
						salarioBase.doubleValue()));
		int quantidadeDependentes = titular.getDependentes().size();

		BigDecimal beneficioSalarioFamilia = valorBeneficioSalarioFamilia
				.multiply(new BigDecimal(quantidadeDependentes));
		movimentacao.setSalarioFamilia(movimentacao.getSalarioFamilia().add(
				beneficioSalarioFamilia));
		movimentacao.setProventos(movimentacao.getProventos().add(
				beneficioSalarioFamilia));
	}

	private void calcularDescontoINSS(Movimentacao movimentacao,
			BigDecimal provento, Date dataMovimentacao) {
		
		movimentacao.setTotalInss(movimentacao.getTotalInss().add(provento)
				.setScale(2, RoundingMode.HALF_EVEN));
		
		if (movimentacao.getTotalInss().compareTo(INSSEnum.obterValorBaseDesconto(
				dataMovimentacao, movimentacao.getTotalInss().doubleValue())) != -1) {
			provento = INSSEnum.obterValorBaseDesconto(dataMovimentacao,
					movimentacao.getTotalInss().doubleValue());
		}
		
		BigDecimal descontoInss = provento.multiply(new BigDecimal(INSSEnum
				.obterTaxa(dataMovimentacao, provento.doubleValue())));
		movimentacao.setInss(descontoInss.setScale(2, RoundingMode.HALF_EVEN));
		movimentacao.setDescontos(movimentacao.getDescontos().add(descontoInss)
				.setScale(2, RoundingMode.HALF_EVEN));
	}

	private void calcularDescontoIRRF(Movimentacao movimentacao,
			BigDecimal provento, Date dataMovimentacao, String matriculaContrato) {

		Contrato contrato = contratoRepository
				.buscarPorMatricula(matriculaContrato);
		Pessoa titular = contrato.getPessoa();

		IRRFEnum irrfEnum = IRRFEnum.obterValoresDeducao(dataMovimentacao,
				provento.doubleValue());

		int quantidadeDependentes = titular.getDependentes().size();
		BigDecimal proventoInss = new BigDecimal(0);
		if (provento.compareTo(INSSEnum.obterValorBaseDesconto(
				dataMovimentacao, provento.doubleValue())) != -1) {
			proventoInss = INSSEnum.obterValorBaseDesconto(dataMovimentacao,
					provento.doubleValue());
		}

		BigDecimal descontoInss = proventoInss.multiply(new BigDecimal(INSSEnum
				.obterTaxa(dataMovimentacao, proventoInss.doubleValue())));

		BigDecimal salarioDescontoIr = provento.subtract(descontoInss);
		BigDecimal totalDescontoDependentes = new BigDecimal(
				quantidadeDependentes * irrfEnum.getDescontoDependente());
		salarioDescontoIr = salarioDescontoIr
				.subtract(totalDescontoDependentes);

		IRRFEnum irrEnumBaseCalculo = IRRFEnum.obterValoresDeducao(
				dataMovimentacao, salarioDescontoIr.doubleValue());
		BigDecimal valorPagoEmpresa = new BigDecimal(
				irrEnumBaseCalculo.getValorPagoEmpresa());
		BigDecimal totalIr = salarioDescontoIr.multiply(new BigDecimal(
				irrEnumBaseCalculo.getTaxa()));
		BigDecimal totalDescontoIrrf = totalIr.subtract(valorPagoEmpresa)
				.setScale(2, RoundingMode.HALF_EVEN);

		movimentacao.setIrrf(movimentacao.getIrrf().add(totalDescontoIrrf));
		movimentacao.setDescontos(movimentacao.getDescontos()
				.add(totalDescontoIrrf).setScale(2, RoundingMode.HALF_EVEN));
	}

	public List<MovimentacaoDTO> consultarPorPeriodoEntidade(Integer mes,
			Integer ano) {
		return this.movimentacaoRepository
				.consultarPorPeriodoEntidade(mes, ano);
	}

	public List<MovimentacaoDTO> consultarPorPeriodo(Integer mes, Integer ano) {
		return this.movimentacaoRepository.consultarPorPeriodo(mes, ano);
	}

	/**
	 * Método valida se existe um contrato para matricula informada, e não
	 * existe movimentação para o contrato no período informado.
	 * 
	 * @param movimentacao
	 * @throws NegocioExeception
	 */
	public void validarPreIncluir(Movimentacao movimentacao)
			throws NegocioExeception {

		Contrato contrato = contratoService.buscarPorMatricula(movimentacao
				.getContrato().getMatricula());

		if (contrato == null) {
			throw new NegocioExeception(
					NAO_EXISTE_CONTRATO_PARA_A_MATRICULA_INFORMADA);
		}

		List<Movimentacao> movimentacoes = movimentacaoRepository
				.consultarPorMesAnoMatricula(movimentacao.getMes(),
						movimentacao.getAno(), movimentacao.getContrato()
								.getMatricula());

		if (movimentacoes != null && !movimentacoes.isEmpty()) {
			throw new NegocioExeception(
					JA_EXISTE_MOVIMENTACAO_PARA_OS_DADOS_INFORMADOS);
		}

		movimentacao.setContrato(contrato);
	}

	@Autowired
	public void setMovimentacaoRepository(
			MovimentacaoRepository movimentacaoRepository) {
		this.movimentacaoRepository = movimentacaoRepository;
	}

	@Autowired
	public void setContratoRepository(ContratoRepository contratoRepository) {
		this.contratoRepository = contratoRepository;
	}

	@Autowired
	public void setContratoService(ContratoService contratoService) {
		this.contratoService = contratoService;
	}

	@Override
	public List<Movimentacao> consultarPorPeriodoCompleto(Integer mes,
			Integer ano) {
		return this.movimentacaoRepository.consultarPorMesAnoCompleto(mes, ano);
	}
}
